﻿/**
 * ...
 * @author Krzysztof Rozalski
 */

package hapi.util.profiler;

import haxe.Timer;

class CodeBlock {
	
	var parent:CodeBlock;
	var innerBlocks:Hash<CodeBlock>;
	var enterStamp:Float;
	var enterMemory:Float;
	var stats:CodeBlockExecutionStats;
	var name:String;
	
	public function new(name) {
		this.name = name;
		this.stats = new CodeBlockExecutionStats();
		this.innerBlocks = new Hash<CodeBlock>();
	}
	
	public inline function enter(name):CodeBlock {
		var t = Timer.stamp();
		var block = findOrCreateBlockByName(name);
		block.markEnter(t);
		return block;
	}
	
	public inline function leave():CodeBlock {
		var t = Timer.stamp();
		registerStats(t);
		return parent != null ? parent : this;
	}
	
	public function summary():String {
		var buf = new StringBuf();
		addSummary(buf);
		return buf.toString();
	}
	
	function addSummary(buf:StringBuf, ?prefix = "") {
		if( stats.containsData() ){
			buf.add(prefix);
			buf.add(name);
			buf.add(" - ");
			if(parent != null) {
				buf.add(countPercent());
				buf.add(" % ");
			}
			buf.add("( ");
			buf.add(stats.toString());
			buf.add(" )");
			prefix += "\t";
		}
		var isParent = innerBlocks.iterator().hasNext();
		if ( isParent ) {
			var duration = stats.overallDuration;
			for ( block in innerBlocks) {
				buf.add("\n");
				block.addSummary(buf, prefix + "\t");
				duration -= block.stats.overallDuration;
			}
			if( duration > 0){
				buf.add("\n");
				buf.add(prefix + "\t~overhead - ");
				var percent = stats.overallDuration > 0 ? Std.string(Math.round(duration * 100 / stats.overallDuration)) : "?";
				buf.add(percent);
				buf.add(" % - OVERALL: ");
				buf.add(Math.round(duration*10)/10);
				buf.add(" ms.");
			}
		}
	}
	
	inline function markEnter(enterStamp):CodeBlock {
		this.enterStamp = enterStamp;
		return this;
	}
	
	function findOrCreateBlockByName(name) {
		if ( name == this.name ) {
			return this;
		}else {
			return findOrCreateInnerBlock(name);
		}
	}
	
	inline function findOrCreateInnerBlock(name) {
		var block:CodeBlock;
		if ( !innerBlocks.exists(name)) {
			block = new CodeBlock(name);
			block.parent = this;
			innerBlocks.set(name, block);
		}else {
			block = innerBlocks.get(name);
		}
		return block;
	}
	
	inline function registerStats(leaveStamp:Float) {
		stats.addCall(leaveStamp - enterStamp);
	}
	
	inline function countPercent() {
		if ( parent.stats.overallDuration <= 0 ) {
			return "?";
		} else {
			var p = stats.overallDuration / parent.stats.overallDuration;
			return Std.string(Math.round(p * 100));
		}
	}
}